use core::arch::naked_asm;
use num::SyscallIndex;
use x86_64::registers::model_specific::{Efer, EferFlags};
use x86_64::registers::model_specific::{LStar, SFMask, Star};
use x86_64::registers::rflags::RFlags;
use x86_64::VirtAddr;

use crate::cpu::gdt::Selectors;

pub use ::syscall::*;

mod sys;
pub use sys::exit;
use sys::*;

pub fn init() {
    let handler_addr = syscall_handler as *const () as u64;

    SFMask::write(RFlags::INTERRUPT_FLAG);
    LStar::write(VirtAddr::new(handler_addr as u64));

    let (code_selector, data_selector) = Selectors::get_kernel_segments();
    let (user_code_selector, user_data_selector) = Selectors::get_user_segments();

    Star::write(
        user_code_selector,
        user_data_selector,
        code_selector,
        data_selector,
    )
    .unwrap();

    unsafe {
        Efer::write(Efer::read() | EferFlags::SYSTEM_CALL_EXTENSIONS);
    }
}

#[naked]
extern "C" fn syscall_handler() {
    unsafe {
        naked_asm!(
            "push rcx",
            "push r11",
            "push rbp",
            "push rbx",
            "push r12",
            "push r13",
            "push r14",
            "push r15",

            // Move the 4th argument in r10 to rcx to fit the C ABI
            "mov rcx, r10",
            "call {syscall_matcher}",

            "pop r15",
            "pop r14",
            "pop r13",
            "pop r12",
            "pop rbx",
            "pop rbp",
            "pop r11",
            "pop rcx",

            "sysretq",
            syscall_matcher = sym syscall_matcher,
        );
    }
}

pub fn malloc(size: usize, align: usize) -> usize {
    let layout = alloc::alloc::Layout::from_size_align(size, align);
    if let Ok(layout) = layout {
        let addr = unsafe { alloc::alloc::alloc(layout) };
        addr as usize
    } else {
        0
    }
}

pub fn free(addr: usize, size: usize, align: usize) -> usize {
    let layout = alloc::alloc::Layout::from_size_align(size, align);
    if let Ok(layout) = layout {
        unsafe { alloc::alloc::dealloc(addr as _, layout) }
        return 0;
    }

    0
}

#[allow(unused_variables)]
pub extern "C" fn syscall_matcher(
    arg1: usize,
    arg2: usize,
    arg3: usize,
    arg4: usize,
    arg5: usize,
    arg6: usize,
) -> usize {
    let syscall_number_raw: usize;
    unsafe { core::arch::asm!("mov {0}, rax", out(reg) syscall_number_raw) };

    match SyscallIndex::from(syscall_number_raw) {
        SyscallIndex::Malloc => malloc(arg1, arg2),
        SyscallIndex::Free => free(arg1, arg2, arg3),
        SyscallIndex::Exit => exit(),
        SyscallIndex::Print => print(arg1 as *const u8, arg2),
        SyscallIndex::Open => open(arg1, arg2, arg3),
        SyscallIndex::Close => close(arg1),
        SyscallIndex::Read => read(arg1, arg2, arg3),
        SyscallIndex::Write => write(arg1, arg2, arg3),
        SyscallIndex::Fsize => fsize(arg1),
        SyscallIndex::IsExited => is_exited(arg1),
        SyscallIndex::Ftype => ftype(arg1),
        SyscallIndex::ListDir => list_dir(arg1, arg2, arg3),
        SyscallIndex::DirItemNum => dir_item_num(arg1, arg2),
        SyscallIndex::IoCtl => ioctl(arg1, arg2, arg3),
        SyscallIndex::LSeek => lseek(arg1, arg2),
        SyscallIndex::Mmap => mmap(arg1, arg2, arg3, arg4),
        SyscallIndex::AllocForDma => alloc_for_dma(arg1),
        SyscallIndex::DeallocForDma => dealloc_for_dma(arg1, arg2),
        SyscallIndex::GetPid => getpid(),
        SyscallIndex::SetInitPid => set_init_pid(arg1),
        SyscallIndex::Execve => execve(arg1, arg2, arg3, arg4),
        SyscallIndex::ChangeCwd => change_cwd(arg1, arg2),
        SyscallIndex::GetCwd => get_cwd(),
    }
}
